package org.javaopenaccess.service.classes;


import java.lang.reflect.Field;
import java.util.Map;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewMethod;
import javassist.NotFoundException;

import org.javaopenaccess.dao.classes.ClassDao;
import org.javaopenaccess.dao.classes.Db4oClassDao;
import org.javaopenaccess.model.ClassMetadata;
import org.javaopenaccess.model.InternalSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sun.reflect.generics.reflectiveObjects.NotImplementedException;

public class DynamicClassServiceImpl implements DynamicClassService{
	private Logger logger = LoggerFactory.getLogger(this.getClass().getName());
	
	
	public Class<?> createType(ClassMetadata classMetadata, InternalSession session) {
		ClassPool pool = ClassPool.getDefault();
		CtClass classDescription = pool.makeClass(classMetadata.getName());
		Class<?> clazz = null;
		try {
			Map<String, String> attrs = classMetadata.getAttributes();
			for (String name : attrs.keySet()) {
				CtClass type = pool.get(attrs.get(name));
				CtField field = new CtField(type, name ,classDescription);
				classDescription.addField(field);
				classDescription.addMethod(CtNewMethod.getter("get" + name.substring(0,1).
						toUpperCase() + name.substring(1), field));
				classDescription.addMethod(CtNewMethod.setter("set" + name.substring(0,1).
						toUpperCase() + name.substring(1), field));
			}
			//compile class and write to a file
			//TODO: writeFile should be actually called, only when 'save' is called
			//for the whole object model? Or should it? maybe only for export?
			//classDescription.writeFile(classDirectory);
			//write file to into a custom classloader, not a default classloader
			clazz = classDescription.toClass(session.getClassLoader(), Class.class.getProtectionDomain());
			//save the class metadata to db
			
			//TODO:This has to be hidden in a factory method!This is bad design. Apply a design pattern here!
			ClassDao classDao = new Db4oClassDao(session);
			classDao.storeClassMetadata(classMetadata);
			//TODO:Should the actual class be saved rather, then the metadata???or both?we need the actual class
			//This actually depends, on the strategy to use the modify class operation, if we have to re-create the data
			//model from scratch we store the metadata, else we store the classes (i guess?)
			
			//FIXME: this doesn't work!!!!!!!
			//classDao.storeClass(clazz);
			classDao.closeTransaction();
		} catch (NotFoundException e) {
			e.printStackTrace();
		} catch (CannotCompileException e) {
			e.printStackTrace();
//		} catch (IOException e) {
//			e.printStackTrace();
		}
		return clazz;
	}
	
	public Object createTypeInstance(Class<?> clazz,InternalSession session, Map<String, Object> values) throws InstantiationException{
		try {
			if (clazz.getDeclaredFields().length != values.size())
					throw new InstantiationException("Too many or too few data parameters provided for the class to instantiated");
			Object instance = clazz.newInstance();
			for (Field field :clazz.getDeclaredFields()){
				//If this won't work, use the setter methods.
				field.setAccessible(true);
				field.set(instance, values.get(field.getName()));
			}
			ClassDao classDao = new Db4oClassDao(session);
			classDao.storeDynamicTypeObject(instance);
			classDao.closeTransaction();
			return instance;
		} catch (IllegalAccessException e) {
			logger.error("instance initalization of custom type failed", e);
			throw new InstantiationException("illegal access to object: " +
					"Root cause: Illegal access exception");
		}
	}
	/**
	 * Three ways of implementing this:
	 * 1. Rude way. Hold 'version' numbers for every 
	 * version of the class eg. org.javaopenaccess.MyType_1
	 * and so one.
	 * 2. Create custom classloaders for schemas. All classes from 
	 * this 'space' are loaded using the special classloader for
	 * that particular schema, becouse only classloaders can be dropped. 
	 * check javaassist classloaders and java API. These
	 * 3. Run the app with debugger open, so java allows class
	 * reloading using the javassist HotSwapper.This might not work, since
	 * major changes to classes are not allowed.
	 */
	@Override
	public void modifyType(Class<?> type) {
		throw new NotImplementedException();
	}

}
